package com._21cn.cms.resource.web.servlet;

import com._21cn.cms.resource.config.PictureConfig;
import com._21cn.cms.resource.entity.Picture;
import com._21cn.cms.resource.service.PictureService;
import com._21cn.cms.resource.utils.PictureManager;
import com._21cn.framework.http.HttpResponseUtil;
import com._21cn.framework.utils.FileUtil;
import com._21cn.framework.utils.image.IMImageInfo;
import com._21cn.framework.utils.image.IMImageUtil;
import com._21cn.framework.utils.image.ImageUtil;
import com.gif4j.GifDecoder;
import com.gif4j.GifImage;
import org.apache.commons.lang.StringUtils;
import org.im4java.core.ConvertCmd;
import org.im4java.core.IMOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.imageio.ImageIO;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

/**
 * User: chenjh Date: 12-4-25 Time: 下午2:46
 */
public class PictureViewServlet extends HttpServlet {

    private static final long serialVersionUID = 8559029383436355290L;

    private static final Logger log = LoggerFactory.getLogger(PictureViewServlet.class);

    private PictureService service;

    private PictureManager manager;

    private static final String FILE_NOT_FOUND = "/images/resource/picture/picture_not_found.jpg";

    private static final String FILE_ERROR = "/images/resource/picture/picture_error.jpg";

    private static final String FILE_NOT_PASS = "/images/resource/picture/1px.png";

    private static final String BAD_REQUEST = "/images/resource/picture/picture_bad_request.jpg";

    private static final String EXPIRE_PIC = "/images/resource/picture/1px.png";

    private PictureConfig pictureConfig = new PictureConfig();          //图片配置项

    /**
     * 转换url为物理位置对应的内部url <br>
     * url: http://img001.21cnimg.com/photos/channel_watermarkPosition/yyyyMMdd/
     * sizeFlag(m300x200) or crop(c缩放比例-x坐标-y坐标-w宽-h高)
     * _rotate(r90,r180,r270)/hashcode.ext
     *
     * @param request  the request send by the client to the server
     * @param response the response send by the server to the client
     * @throws ServletException if an error occurred
     * @throws IOException      if an error occurred
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String thumb = request.getParameter("thumb");

        // 获取请求的地址
        String pathInfo = request.getPathInfo();

        if (StringUtils.isEmpty(pathInfo) || !StringUtils.isAsciiPrintable(pathInfo)) {
            log.error("error format: no pathInfo !");
            sendImageNotFound(FILE_NOT_FOUND, pictureConfig.getPictureNotFoundCacheTime(), request, response);
            return;
        }

        // 分割地址参数
        String[] info = pathInfo.substring(1).split("/");

        if (info.length < 4) {
            log.error("bad path info! pathInfo:" + pathInfo + " Refer:" + request.getHeader("Referer"));
            sendImageNotFound(BAD_REQUEST, pictureConfig.getPictureErrorCacheTime(), request, response);
            return;
        }

        if (log.isDebugEnabled())
            log.debug("pathInfo:" + pathInfo + ",info.length:" + info.length + ",info[0]:" + info[0]);

        // 获取hashcode
        String hashcode = StringUtils.upperCase(info[info.length - 1].split("\\.")[0]);

        if (hashcode.length() != 32) {
            log.error("bad request! hashcode:" + hashcode);
            sendImageNotFound(BAD_REQUEST, pictureConfig.getPictureErrorCacheTime(), request, response);
            return;
        }

        // 获取动态处理图片的参数
        String[] processParam = info[info.length - 2].contains("_") ? info[info.length - 2].split("_")
                : new String[]{info[info.length - 2]};
        if (log.isDebugEnabled())
            log.debug("info[info.length - 2]:" + info[info.length - 2] + " ,processParam.length:" + processParam.length
                    + " ,processParam[0]" + processParam[0]);

        // 根据hashcode获取图片对象
        Picture p = service.getByHashcode(hashcode);

        if (p == null) {
            log.error("can not find hashcode:" + hashcode);
            sendImageNotFound(FILE_NOT_FOUND, pictureConfig.getPictureNotFoundCacheTime(), request, response);
            return;
        }
        if(pathInfo.contains(hashcode+".webp")){
            if(p.getExtName()!=null&&p.getExtName().equalsIgnoreCase("gif")){
                pathInfo=pathInfo.replace(hashcode+".webp", hashcode+"."+p.getExtName()); 
            }else{
                p.setExtName("webp");
            }
        }

        String filePath = manager.getPictureFullPath(p);
        if (log.isDebugEnabled())
            log.debug("filePath:" + filePath);

        // 判断图片物理是否存在
        if (!new File(filePath).exists()) {
            log.error("file not found, path:" + filePath);
            sendImageNotFound(FILE_NOT_FOUND, pictureConfig.getPictureNotFoundCacheTime(), request, response);
            return;
        }

        try {
            IMImageUtil im = new IMImageUtil();
            File imageFile = new File(filePath);
            String destPath = pictureConfig.getCacheFilePath() + pathInfo.substring(1);
            String tempFilePath = "/tmp/temp_pic_" + UUID.randomUUID().toString() + "." + p.getExtName();
            if (destPath.contains("?")) {
                destPath = destPath.substring(0, destPath.indexOf("?"));
            }

            if (processParam[0].equalsIgnoreCase("o")&&!p.getExtName().equalsIgnoreCase("gif")) { // 输出原图(original)
                if(p.getExtName().equalsIgnoreCase("webp")){
                    File destFolder = new File(destPath.substring(0, destPath.lastIndexOf("/")));
                    destFolder.mkdirs();
                    cropImageWithQ(filePath, destPath, 0, 0);
                    sendImage(p, new File(destPath), pictureConfig.getPictureCacheTime(), response);
                }else{
                    sendImage(p, imageFile, pictureConfig.getPictureCacheTime(), response);
                }
                return;
            }

            Set<String> tempPathSet = new HashSet<String>();
            if(p.getExtName().equalsIgnoreCase("gif")){
                if(thumb!=null&&thumb.equalsIgnoreCase("1")){
                    destPath=destPath.replace(".gif", ".jpg");
                    String oFilePath = destPath.replace("/"+processParam[0]+"/", "/o/");
                    boolean isSuccess = true;
                    if (!new File(oFilePath).exists()) {
                       isSuccess = getGifThumb(filePath, oFilePath);
                       if(isSuccess){
                           filePath=oFilePath;
                           p.setExtName("jpg");    
                       }
                    }else{
                        filePath=oFilePath;
                        p.setExtName("jpg");  
                    }
                    if (!isSuccess||processParam[0].equalsIgnoreCase("o")){
                        sendImage(p, new File(filePath) , pictureConfig.getPictureCacheTime(), response);
                        return;
                    }
                }else{
                    sendImage(p, imageFile, pictureConfig.getPictureCacheTime(), response);
                    return;
                }
            }
            // 图片旋转
            if (processParam.length > 1) {
                double degree = Double.valueOf(processParam[1].substring(1));
                if (degree > 0 && degree < 360) {
                    im.rotate(filePath, tempFilePath, degree, "rgb(255,255,255)", null);
                    imageFile = new File(tempFilePath);
                    filePath = new String(tempFilePath);
                    tempPathSet.add(tempFilePath);
                    if (log.isDebugEnabled())
                        log.debug("picture rotate:" + degree);
                }
            }
            if(processParam[0].startsWith("q")){
                tempFilePath = "/tmp/temp_pic_" + UUID.randomUUID().toString() + "." + p.getExtName();
                String[] whStr = processParam[0].substring(1).split("x");
                int w = Integer.parseInt(whStr[0]);
                int h = w;
                if (whStr.length > 1) {
                    h = Integer.parseInt(whStr[1]);
                }
                cropImageWithQ(filePath, tempFilePath, w, h);
                imageFile = new File(tempFilePath);
                tempPathSet.add(tempFilePath);
            }
            else if (processParam[0].equalsIgnoreCase("s")) { // 输出小图(small)
                    tempFilePath = "/tmp/temp_pic_" + UUID.randomUUID().toString() + "." + p.getExtName() + "." + p.getExtName();
                    im.cropMinImage(filePath, pictureConfig.getThumbMaxWidth(), pictureConfig.getThumbMaxHeight(), tempFilePath);
                    imageFile = new File(tempFilePath);
                    tempPathSet.add(tempFilePath);
//                }
            } else if (processParam[0].equalsIgnoreCase("m")) { // 输出中图(mid)
                    tempFilePath = "/tmp/temp_pic_" + UUID.randomUUID().toString() + "." + p.getExtName();
                    im.scale(filePath, tempFilePath, pictureConfig.getMidMaxWidth(), pictureConfig.getMidMaxHeight());
                    imageFile = new File(tempFilePath);
                    tempPathSet.add(tempFilePath);
//                }
            } else if (processParam[0].startsWith("s")) {
                String[] whStr = processParam[0].substring(1).split("x");
                int w = Integer.parseInt(whStr[0]);
                int h = w;
                if (whStr.length > 1) {
                    h = Integer.parseInt(whStr[1]);
                }
                if (log.isDebugEnabled()) {
                    log.debug("s -- w:" + w + " ,h:" + h);
                }
                tempFilePath = "/tmp/temp_pic_" + UUID.randomUUID().toString() + "." + p.getExtName();
                im.cropMinImage(filePath, w, h, tempFilePath);
                imageFile = new File(tempFilePath);
                tempPathSet.add(tempFilePath);
            } else if (processParam[0].startsWith("m")) { // 输出定制中图(如:m300或m200x100)
                String[] whStr = processParam[0].substring(1).split("x");
                int w = Integer.parseInt(whStr[0]);

                // m模式下如果只传宽度的话高度就取manager.getMaxHeight，这样的话可以适配瀑布流样式输出的要求。
                int h = pictureConfig.getMaxHeight();
                if (whStr.length > 1)
                    h = Integer.parseInt(whStr[1]);

                if (log.isDebugEnabled()) {
                    log.debug("m -- w:" + w + " ,h:" + h);
                }
                tempFilePath = "/tmp/temp_pic_" + UUID.randomUUID().toString() + "." + p.getExtName();
                im.scale(filePath, tempFilePath, w, h);
                imageFile = new File(tempFilePath);
                tempPathSet.add(tempFilePath);
            } else if (processParam[0].startsWith("c")) { // 按一定比例缩放并切割图片
                String[] cropStr = processParam[0].substring(1).split("-");
                int pro = Integer.valueOf(cropStr[0]); // 比例
                int xPos = Integer.valueOf(cropStr[1]); // x坐标
                int yPos = Integer.valueOf(cropStr[2]); // y坐标
                int w = Integer.valueOf(cropStr[3]); // 长度
                int h = w; // 宽度
                if (cropStr.length > 4) {
                    h = Integer.valueOf(cropStr[4]);
                }

                if (pro < 100) {
                    tempFilePath = "/tmp/temp_pic_" + UUID.randomUUID().toString() + "." + p.getExtName();
                    im.scale(filePath, tempFilePath, p.getWidth() * pro / 100, p.getHeight() * pro / 100);
                    imageFile = new File(tempFilePath);
                    filePath = new String(tempFilePath);
                    tempPathSet.add(tempFilePath);
                }
                // 如果w或h为0就只按比例缩放
                if (w != 0 && h != 0) {
                    tempFilePath = "/tmp/temp_pic_" + UUID.randomUUID().toString() + "." + p.getExtName();
                    if (log.isDebugEnabled()) {
                        log.debug("xPos:" + xPos + " yPos:" + yPos + " w:" + w + " h:" + h);
                    }
                    im.setMaxHeight(0);
                    im.setMaxWidth(0);
                    im.cropImage(filePath, tempFilePath, xPos, yPos, w, h);
                    imageFile = new File(tempFilePath);
                    tempPathSet.add(tempFilePath);
                }
            } else if (processParam[0].startsWith("r")) { // 旋转
                double degree = Double.valueOf(processParam[0].substring(1));
                if (degree > 0 && degree < 360) {
                    tempFilePath = "/tmp/temp_pic_" + UUID.randomUUID().toString() + "." + p.getExtName();
                    im.rotate(filePath, tempFilePath, degree, "rgb(255,255,255)", null);
                    imageFile = new File(tempFilePath);
                    tempPathSet.add(tempFilePath);
                }
            }

            // 输出图片
            if (imageFile != null) {

                //创建目录
                File destFolder = new File(destPath.substring(0, destPath.lastIndexOf("/")));
                if (log.isDebugEnabled())
                    log.debug("destFolder:" + destFolder);
                destFolder.mkdirs();

                if (imageFile.getPath().contains("/tmp/")||
                        imageFile.getPath().contains("\\tmp\\")) {//windows兼容
                    if (log.isDebugEnabled())
                        log.debug("move:" + imageFile.getPath() + " to " + destPath);
                    FileUtil.move(imageFile.getPath(), destPath);
                } else {
                    if (log.isDebugEnabled())
                        log.debug("no move:" + imageFile.getPath());
                    destPath = imageFile.getPath();
                }
                for (String tempPath : tempPathSet) {
                    log.debug("delete:" + tempPath);
                    if (tempPath.contains("/tmp/"))
                        new File(tempPath).delete();
                }
                sendImage(p, new File(destPath), pictureConfig.getPictureCacheTime(), response);
                return;
            }

        } catch (OutOfMemoryError e) {
            log.error("OutOfMemoryError: " + e.getMessage());
            e.printStackTrace();
            sendImageNotFound(FILE_ERROR, pictureConfig.getPictureErrorCacheTime(), request, response);
            return;
        } catch (Exception e) {
            e.printStackTrace();
            sendImageNotFound(FILE_ERROR, pictureConfig.getPictureErrorCacheTime(), request, response);
            return;
        }

        sendImageNotFound(FILE_ERROR, pictureConfig.getPictureErrorCacheTime(), request, response);

    }

    /**
     * 输出图片
     *
     * @param p
     * @param image
     * @param cacheTime
     */
    private void sendImage(Picture p, BufferedImage image, int cacheTime, HttpServletResponse response) {
        try {
            if (cacheTime > 0) {
                HttpResponseUtil.setCacheHeader(response, cacheTime, p.getModifyTime());
            } else {
                HttpResponseUtil.setNoCacheHeader(response);
            }
            if (p.getExtName().equalsIgnoreCase("jpg"))
                response.setContentType("image/jpeg");
            else
                response.setContentType("image/" + p.getExtName());
            ImageIO.write(image, p.getExtName(), response.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 输出未找到和被封杀的图片
     *
     * @param fileName
     * @param cacheTime
     */
    private void sendImageNotFound(String fileName, int cacheTime, HttpServletRequest request, HttpServletResponse response) {
        StringBuilder path = new StringBuilder(request.getServletContext().getRealPath(fileName));
        try {
            if (log.isDebugEnabled())
                log.debug("sendImageNotFound image path:" + path.toString());
            Picture p = new Picture();
            p.setExtName("png");
            sendImage(p, ImageUtil.getBufferedImage(path.toString()), cacheTime, response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 输出图片
     *
     * @param p
     * @param pictureFile
     * @param cacheTime
     */
    private void sendImage(Picture p, File pictureFile, int cacheTime, HttpServletResponse response) {

        try {
            if (cacheTime > 0) {
                HttpResponseUtil.setCacheHeader(response, cacheTime, p.getModifyTime());
                response.setHeader("Content-Length", String.valueOf(pictureFile.length()));
            } else {
                HttpResponseUtil.setNoCacheHeader(response);
            }
            if (p.getExtName().equalsIgnoreCase("jpg"))
                response.setContentType("image/jpeg");
            else
                response.setContentType("image/" + p.getExtName());
            if (log.isDebugEnabled())
                log.debug(pictureFile.getPath());
            FileUtil.putFileToStream(pictureFile, response.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void destroy() {
        super.destroy();
    }

    /**
     *1、如果源图宽高都小于目标宽高，则只压缩图片，不做切割
     * 2、如果源图宽高都大于目标宽度，则根据宽度等比压缩后再根据中心点居中切割 3、其它条件下，则压缩图片（不做缩放）后再根据中心点居中切割
     *
     *
     * @param srcPath
     *            源图路径
     * @param desPath
     *            切割图片保存路径
     * @param pw
     *            中心点x坐标
     * @param ph
     *             中心点y坐标
     * @param dw
     *            切割目标宽度
     * @param dh
     *            切割目标高度
     * @throws Exception
     */
    private static void cropImage(String srcPath, String desPath, int pw, int ph, int dw, int dh)  {
        try{ IMImageInfo info = new IMImageInfo(srcPath, true);
            int sw = info.getImageWidth();
            int sh = info.getImageHeight();
            if (sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0)
                return;
            IMOperation op = new IMOperation();
            op.colorspace("RGB");
            op.p_profile("*");
            op.addImage(srcPath);

            if ((sw <= dw) && (sh <= dh)) // 如果源图宽度和高度都小于目标宽高，则仅仅压缩图片
            {
                op.resize(sw, sh);
            }

            if ((sw <= dw) && (sh > dh))// 如果源图宽度小于目标宽度，并且源图高度大于目标高度
            {
                op.resize(sw, sh); // 压缩图片
                op.append().crop(sw, dh, 0, (ph-dh/2>0)?(sh-ph>dh/2?(ph-dh/2):sh-dh):0);
            }

            if ((sw > dw) && (sh <= dh))// 如果源宽度大于目标宽度，并且源高度小于目标高度
            {
                op.resize(sw, sh);
                op.append().crop(dw, sh, (pw-dw/2>0)?(sw-pw>dw/2?(pw-dw/2):sw-dw):0, 0);
            }

            if (sw > dw && sh > dh) // 如果源图宽、高都大于目标宽高
            {
                float ratiow = (float) dw / sw; // 宽度压缩比
                float ratioh = (float) dh / sh; // 高度压缩比

                if (ratiow >= ratioh) // 宽度压缩比小（等）于高度压缩比（是宽小于高的图片）
                {
                    int ch = (int) (ratiow * sh); // 压缩后的图片高度
                    op.resize(dw, null); // 按目标宽度压缩图片
                    int k = (int)((float)ch/sh*ph);
                    op.append().crop(dw, dh, 0, (k-dh/2>0)?(ch-k>dh/2?(k-dh/2):ch-dh):0);
                } else // （宽大于高的图片）
                {
                    int cw = (int) (ratioh * sw); // 压缩后的图片宽度
                    op.resize(cw, null); // 按计算的宽度进行压缩
                    int k = (int)((float)cw/sw*pw);
                    op.append().crop(dw, dh, (k-dw/2)>0?(cw-k>dw/2?(k-dw/2):cw-dw):0, 0);
                }
            }
            op.addImage(desPath);
            ConvertCmd convert = new ConvertCmd(true);
            convert.run(op);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    
    private static void cropImageWithQ(String srcPath, String desPath,int dw, int dh)  {
        try {
            IMImageInfo info = new IMImageInfo(srcPath, true);
            int sw = info.getImageWidth(0);
            int sh = info.getImageHeight(0);
            IMOperation op = new IMOperation();
            op.colorspace("RGB");
            op.p_profile("*");
            op.addImage(srcPath);
            dw=dw>0?dw:sw;
            dh=dh>0?dh:sh;
            if((sw <= dw) && (sh <= dh))  //如果源图宽度和高度都小于目标宽高，则仅仅压缩图片  
            {  
                op.resize(sw, sh);  
                float ratiow = (float)dw / sw;  //宽度压缩比  
                float ratioh = (float)dh / sh;  //高度压缩比  
                      
                if(ratiow >= ratioh) //宽度压缩比小（等）于高度压缩比（是宽小于高的图片）  
                {  
                    int ch = (int)(ratiow * sh);    //压缩后的图片高度  
                    op.scale(dw, ch);      
                    op.append().crop(dw, dh, 0, (ch > dh)?((ch - dh)/2):0);  //根据高度居中切割压缩后的图片  
                }  
                else    //（宽大于高的图片）  
                {  
                    int cw = (int)(ratioh * sw);    //压缩后的图片宽度  
                    op.scale(cw, dh);    
                    op.append().crop(dw, dh, (cw > dw)?((cw - dw)/2):0, 0);  //根据宽度居中切割压缩后的图片  
                         
                }  
            }  
                  
            if((sw <= dw) && (sh > dh))//如果源图宽度小于目标宽度，并且源图高度大于目标高度  
            {  
                int h = sh*dw/sw;
                op.scale(dw, h);
                op.append().crop(dw, dh, 0, (h-dh)/2);//切割图片             
            }  
                  
            if((sw > dw) && (sh <= dh))//如果源宽度大于目标宽度，并且源高度小于目标高度  
            {  
                int w = sw*dh/sh;
                op.scale(w, dh);
                op.append().crop(dw, dh, (w - dw)/2, 0);  
            }  
                  
            if(sw > dw && sh > dh)    //如果源图宽、高都大于目标宽高  
            {  
                float ratiow = (float)dw / sw;  //宽度压缩比  
                float ratioh = (float)dh / sh;  //高度压缩比  
                      
                if(ratiow >= ratioh) //宽度压缩比小（等）于高度压缩比（是宽小于高的图片）  
                {  
                    int ch = (int)(ratiow * sh);    //压缩后的图片高度  
                          
                    op.resize(dw, null);    //按目标宽度压缩图片  
                    op.append().crop(dw, dh, 0, (ch > dh)?((ch - dh)/2):0);  //根据高度居中切割压缩后的图片  
                }  
                else    //（宽大于高的图片）  
                {  
                    int cw = (int)(ratioh * sw);    //压缩后的图片宽度  
                    op.resize(cw, null);    //按计算的宽度进行压缩  
                    op.append().crop(dw, dh, (cw > dw)?((cw - dw)/2):0, 0);  //根据宽度居中切割压缩后的图片  
                         
                }  
            } 
            op.addImage(desPath);
            ConvertCmd convert = new ConvertCmd(true);
            convert.run(op);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static boolean getGifThumb(String srcPath, String desPath)  {
        boolean isSuccess = true;
        File destFolder = new File(desPath.substring(0, desPath.lastIndexOf("/")));
        try {
            destFolder.mkdirs();
            IMOperation op = new IMOperation();
            op.colorspace("RGB");
            op.p_profile("*");
            op.addImage(srcPath);
            op.addImage(desPath);
            ConvertCmd convert = new ConvertCmd(true);
            convert.run(op);
        } catch (Exception e) {
            try {
                GifImage gifImage = GifDecoder.decode(new File(srcPath));
                BufferedImage image = gifImage.getFrame(0).getAsBufferedImage();
                ImageIO.write(image, "jpg", new File(desPath));
            } catch (Exception e2) {
                isSuccess = false;
                e2.printStackTrace();
            }
        }
        return isSuccess;
    } 

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(servletConfig.getServletContext());
        service = (PictureService) wac.getBean("pictureService");
        manager = (PictureManager) wac.getBean("pictureManager");
    }

}
